Skip to content

fix(campaigns): show above-header prompts without JS delay (NPPM-2934)#449

Open
adekbadek wants to merge 7 commits into
mainfrom
nppm-2934
Open

fix(campaigns): show above-header prompts without JS delay (NPPM-2934)#449
adekbadek wants to merge 7 commits into
mainfrom
nppm-2934

Conversation

@adekbadek

@adekbadek adekbadek commented Jun 29, 2026

Copy link
Copy Markdown
Member

All Submissions:

  • Have you followed the Newspack Contributing guidelines?
  • Does your code follow the WordPress coding standards and VIP Go coding standards?
  • Have you checked to ensure there aren't other open Pull Requests for the same update/change?

Changes proposed in this Pull Request:

Above-header Campaign Prompts were appearing only after a multi-second delay on sites running Perfmatters, because the Newspack Perfmatters defaults delay the prompt's view script until the reader's first interaction. The script reveals the prompt (it strips the server-rendered hidden class), so until it runs the prompt is invisible — hurting both visibility and CLS. Reported by CT Mirror and Outlier Media (NPPM-2934).

This PR:

  1. Renders published above-header prompts immediately under Perfmatters. When the site has at least one published above-header prompt, the Perfmatters integration removes the reveal-path scripts (newspack-popups, window.newspack, newspack-plugin) from the JS delay queue and adds newspack-popups/newspack-plugin to the defer exclusions. Every other Newspack script stays delayed, so the rest of the site keeps the performance benefit. Detection is Newspack_Popups_Model::has_published_above_header_prompts() — transient-cached (the filter runs on essentially every front-end request) and invalidated when a prompt is saved, transitions status, has its placement meta changed, or is deleted.
  2. Warns about the CLS tradeoff. A non-dismissible notice in the prompt editor (when placement is "Above site header") and in the Campaigns wizard (when a published above-header prompt exists) explains that above-header prompts shift layout as they load and suggests keeping them short or using an overlay.

The reveal-script set is intentionally minimal (only what gates the prompt becoming visible) rather than un-delaying all Newspack JS. Detection deliberately keys on published + above_header placement only, not full display eligibility (active campaign group / activation window / segment match) — replicating the Inserter's per-request logic on this hot path is too expensive, and the tradeoff is fail-safe: a published-but-never-shown prompt only forfeits the JS-delay optimization, it breaks nothing. This is documented at the detection method.

editor-cls-notice-full wizard-cls-notice-full

Closes NPPM-2934.

How to test the changes in this Pull Request:

  1. On a site with Perfmatters active and the Newspack Perfmatters defaults applied, create and publish an above-header Campaign Prompt targeting the home page.
  2. Load the home page in a fresh/incognito session. The prompt should appear immediately on load, not after a few seconds.
  3. Confirm newspack-popups / window.newspack are no longer in Perfmatters' "Delay JavaScript" list and that newspack-popups / newspack-plugin are in the "Exclude from Delay/defer" set (or inspect via apply_filters( 'option_perfmatters_options', [] )).
  4. Unpublish/trash the above-header prompt and reload: the scripts return to the delay queue (other Newspack scripts were delayed throughout).
  5. In the prompt editor, set placement to "Above site header" → a CLS warning notice appears under the placement control; switch away → it disappears.
  6. In Audience → Campaigns, the same warning appears at the top while a published above-header prompt exists.

Note: on Atomic/WPCloud, already-cached pages keep the old script config until their own page-cache entries purge/expire.

Other information:

  • Have you added an explanation of what your changes do and why you'd like us to include them?
  • Have you written new tests for your changes, as applicable? (AboveHeaderDetectionTest in newspack-popups; Newspack_Test_Perfmatters in newspack-plugin.)
  • Have you successfully run tests with your changes locally? (Full newspack-popups suite green; new perfmatters tests green; browser-verified on an isolated env.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Claude-Session: https://claude.ai/code/session_01269AjGvnGyzCa3USYaTjbU
Copilot AI review requested due to automatic review settings June 29, 2026 16:04

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@adekbadek
adekbadek requested a review from Copilot June 29, 2026 16:05

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@adekbadek
adekbadek marked this pull request as ready for review June 30, 2026 08:50
@adekbadek
adekbadek requested a review from a team as a code owner June 30, 2026 08:50

@dkoo dkoo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Release risk: LOW. Clean, well-documented change. The transient sentinel handling (false vs '0') is correct, the cache-invalidation hook set matches the detection query's predicates (status + placement meta), the editor-notice useEffect cleanup leaves no stale notices, and the new tests exercise the real production invalidation paths rather than a test-only flush. The has_published_above_header_prompts() docblock is unusually candid about both the coarse-detection trade-off and the page-cache staleness limitation.

One correctness gap is worth addressing before merge: the JS-delay exclusion only removes the reveal scripts from Newspack's own default contribution, not from a publisher's already-persisted delay_js_inclusions, so on sites that have saved Perfmatters' JS-delay settings through its UI the fix can silently no-op. The two other inline notes are optional hardening. Details inline.

Comment thread plugins/newspack-plugin/includes/plugins/class-perfmatters.php Outdated
Comment thread plugins/newspack-plugin/includes/plugins/class-perfmatters.php
Comment thread plugins/newspack-popups/includes/class-newspack-popups.php
@chickenn00dle chickenn00dle self-assigned this Jul 17, 2026

@chickenn00dle chickenn00dle left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Release risk: LOW. Clean, well-documented change and everything runtime-checkable passes — the merged-list array_diff correctly subtracts the reveal scripts (including from a publisher's already-saved delay list while preserving their own entries), the request memo issues zero repeat queries, the re-entrancy guard and transient '0' sentinel are both correct, cache invalidation fires on status transition / placement-meta / delete / trash, and both plugins' new tests are green.

Nothing blocking. One thing to handle before merge and three optional cleanups:

  • Merge conflict with main. GitHub shows this PR as conflicting: an add/add conflict on tests/unit-tests/perfmatters.php. class-perfmatters.php auto-merges cleanly, so main's newer WooCommerce-scripts veto (NPPM-193) source survives — but its three WC-veto tests live only in main's copy of that test file. Resolving the conflict by keeping this branch's version wholesale would silently drop them and leave that method untested; union both test sets when you rebase.
  • Optional hardening: a try/finally so an exception mid-query can't leave the re-entrancy flag stuck true for the rest of the request (fail-safe if skipped), 'orderby' => 'none' on the existence query to skip a filesort, and an assertion pinning that window.newspack stays out of the defer exclusions.

@@ -0,0 +1,107 @@
<?php

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: suggestion

GitHub reports this PR as CONFLICTING. The merge-base predates NPPM-193 (the Perfmatters WooCommerce-scripts veto), which landed on main afterward and created this same test file with three WC-veto tests (test_vetoes_when_wc_content_present, test_passes_through_when_no_wc_content, test_ignore_defaults_passes_through). This branch independently created the file with the five above-header tests, so it's an add/add conflict. class-perfmatters.php auto-merges cleanly, so the veto source (maybe_keep_woocommerce_assets) survives — but those three tests live only in main's copy of this file, and resolving the conflict by keeping this branch's version wholesale would silently drop them, leaving the surviving method untested. When you rebase/merge main, union the two sets of tests rather than overwriting.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed the diagnosis: NPPM-193 (commit 5aa59b0) is on origin/main but not in this branch, so neither maybe_keep_woocommerce_assets nor the three WC-veto tests exist here yet. This is genuinely a merge to resolve, not something I can fix in-place – hand-adding the WC-veto tests to this file would fail (the veto method is absent on this branch) and would still collide on the eventual merge. Flagging for a human to run the git merge origin/main and union both test sets (the five above-header tests here + the three WC-veto tests from main) into one class. Leaving this thread open.

Comment thread plugins/newspack-popups/includes/class-newspack-popups-model.php
Comment thread plugins/newspack-popups/includes/class-newspack-popups-model.php Outdated
Comment thread plugins/newspack-plugin/tests/unit-tests/perfmatters.php

@dkoo dkoo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@adekbadek thanks for addressing the initial review feedback! Looks like our reviewer skill crossed paths with another review from @chickenn00dle—it'd be interesting to see if any of the feedback from this second review surfaced anything the first review missed. Redundancy is never a bad thing when it comes to code reviews 😄

From my perspective, the PR is approved.


Re-reviewed the follow-up commit (11523f45) against my earlier notes.

Release risk: LOW.

The delay-list fix is correct: the reveal scripts are now subtracted from the merged delay_js_inclusions (persisted option + Newspack's own scripts_to_delay()), so they're stripped even on a site whose saved Perfmatters settings already round-tripped them into storage — the case my earlier comment flagged and the old test masked. test_reveal_scripts_undelayed_when_already_in_saved_option covers exactly that non-empty-option path, and the negative test confirms the list is preserved when no above-header prompts exist. The defer/js_exclusions path is purely additive (excluding a script from deferral has no re-add hazard), so it correctly needs no equivalent subtraction.

The request-level memo + re-entrancy guard in has_published_above_header_prompts() is sound: the memo is written only on the outer pass, the nested re-entrant read returns false without poisoning it, and flush_above_header_cache() clears the memo so in-request invalidation via the save/meta/status/delete hooks still works.

Both of my other earlier points are addressed too — the memoization is in place, and the meta-hook scope is justified in the added comment. Approving. One optional nit inline; nothing blocking.

Comment thread plugins/newspack-popups/includes/class-newspack-popups-model.php
@adekbadek
adekbadek requested a review from chickenn00dle July 20, 2026 08:49
@adekbadek

Copy link
Copy Markdown
Member Author

@chickenn00dle Re-requesting review – the changes you asked for have been pushed and need a look before this merges:

  • fix(campaigns): exception-safe above-header detection guard (NPPM-2934)

@github-actions github-actions Bot added [Status] Approved Pull request has been approved and removed [Status] Needs Review labels Jul 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Status] Approved Pull request has been approved

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants